﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using StockModel;
using Utils;
using Utils.Wpf;

namespace StockAnalyst2.StockGrid
{
	internal class GridVM : ViewModelBase, IDisposable
	{
		#region Attributes

		private readonly ObservableCollection<StockGroup> _model;

		private readonly ObservableCollection<RowVM> _placeHolder;
		private readonly ObservableCollection<RowVM> _realRows;
		private ObservableCollection<RowVM> _rows;
		private readonly List<StockGroup> _groups;

		private readonly object _realRowsLock;

		private bool _isVisible;

		private readonly GridRowManager _gridRowManager;
		
		#endregion

		#region Properties

		public ObservableCollection<RowVM> Rows
		{
			get
			{
				return _rows;
			}
			private set
			{
				if (_rows != value)
				{
					_rows = value;
					base.OnPropertyChanged("Rows");
				}
			}
		}

		public AllColumnsOptionsVM Columns { get; private set; }

		public bool IsPlaceHolderVisible
		{
			get
			{
				return _gridRowManager.IsPlaceHolderVisible;
			}
			set
			{
				if (_gridRowManager.IsPlaceHolderVisible != value)
				{
					_gridRowManager.IsPlaceHolderVisible = value;
					_gridRowManager.RefreshRowsVisibility();
					this.Rows = _gridRowManager.IsPlaceHolderVisible ? _placeHolder : _realRows;
				}
			}
		}

		public string NameFilter
		{
			get
			{
				return _gridRowManager.NameFilter;
			}
			set
			{
				if (_gridRowManager.NameFilter != value)
				{
					_gridRowManager.NameFilter = value;
					_gridRowManager.RefreshRowsVisibility();
				}
			}
		}

		public bool IsVisible
		{
			get
			{
				return _isVisible;
			}
			set
			{
				if (_isVisible != value)
				{
					_isVisible = value;
					base.OnPropertyChanged("IsVisible");

					// Process only the changes on the columns if the grid is visible
					if (_isVisible)
					{
						this.Columns.PropertyChanged += this.Columns_PropertyChanged; // Disposed
					}
					else
					{
						this.Columns.PropertyChanged -= this.Columns_PropertyChanged;
					}
				}
			}
		}

		#endregion

		#region Public Methods

		public GridVM(ObservableCollection<StockGroup> groups, AllColumnsOptions allColumnsOptions)
		{
			_placeHolder = new ObservableCollection<RowVM>();
			_realRows = new ObservableCollection<RowVM>();
			_groups = new List<StockGroup>();
			
			_realRowsLock = new object();

			_gridRowManager = new GridRowManager(_realRowsLock, _realRows, base.Dispatcher);
			_gridRowManager.Start();			

			_model = groups;
			_model.CollectionChanged += this.Groups_CollectionChanged; // Disposed

			_placeHolder.Add(new RowVM("---", "---", "---", new QuotationDailyInfo(DateTime.MinValue)) { IsVisible = true });

			this.Columns = new AllColumnsOptionsVM(allColumnsOptions);
			this.Rows = _placeHolder;
		}

		public void Stop()
		{
			_gridRowManager.Stop();
		}
		
		#endregion

		#region Event Handlers

		private void Groups_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				foreach (StockGroup addedGroup in e.NewItems)
				{
					addedGroup.Items.CollectionChanged += this.Items_CollectionChanged;
					_groups.Add(addedGroup);
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Remove)
			{
				foreach (StockGroup removedGroup in e.OldItems)
				{
					removedGroup.Items.CollectionChanged -= this.Items_CollectionChanged;
					_groups.Remove(removedGroup);
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Reset)
			{
				foreach (StockGroup stockGroup in _groups)
				{
					stockGroup.Items.CollectionChanged -= this.Items_CollectionChanged;
				}
				_groups.Clear();
			}
		}

		private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			ObservableCollection2<StockGroupItem> itemCollection = (ObservableCollection2<StockGroupItem>)sender;

			StockGroup group = null;

			foreach (StockGroup stockGroup in _model)
			{
				ObservableCollection2<StockGroupItem> groupItems = stockGroup.Items.GetReadLock();
				try
				{
					if (object.ReferenceEquals(groupItems, itemCollection))
					{
						group = stockGroup;
						break;
					}
				}
				finally
				{
					stockGroup.Items.FreeReadLock();
				}
			}

			if (group == null)
			{
				throw new InvalidDataException("Cannot find the group that created the group items collection changed");	
			}

			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				lock (_realRowsLock)
				{
					foreach (StockGroupItem newItem in e.NewItems)
					{
						Stock item = newItem.Item;
						QuotationDailyInfo dailyInfo = item.QuotationCalendar.GetLatestAvailableDay();
						// Currently a group will have always only one day loaded
						_gridRowManager.AddRowVM(group.Name.ToString(), item.Id.Name, item.Id.Code, dailyInfo);
					}
					_gridRowManager.Sort();
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Remove)
			{
				lock (_realRowsLock)
				{
					foreach (StockGroupItem removedItem in e.OldItems)
					{
						Stock item = removedItem.Item;
						RowVM rowVM = _realRows.First(r => r.Symbol.Value == item.Id.Code);
						_gridRowManager.RemoveRowVM(rowVM);
					}
				}
			}
			else if (e.Action == NotifyCollectionChangedAction.Reset)
			{
				lock (_realRowsLock)
				{
					IEnumerable<RowVM> rows = _realRows.Where(r => r.Group.Value == @group.Name.ToString());
					foreach (RowVM rowVM in rows)
					{
						_gridRowManager.RemoveRowVM(rowVM);
					}
				}
			}
		}

		private void Columns_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
		{
			AllColumnsOptionsDataSource.Save(this.Columns.Model);
			Properties.Settings.Default.Save();
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				this.Columns.PropertyChanged -= Columns_PropertyChanged;
				this.Columns.Dispose();
				
				_model.CollectionChanged -= this.Groups_CollectionChanged;

				foreach (StockGroup stockGroup in _groups)
				{
					stockGroup.Items.CollectionChanged -= this.Items_CollectionChanged;
				}

				foreach (RowVM rowVM in _placeHolder)
				{
					rowVM.Dispose();
				}

				if (_gridRowManager != null && !_gridRowManager.Stopped)
				{
					_gridRowManager.Stop();
				}
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~GridVM()
		{
			this.Dispose(false);
		}

		#endregion
	}
}
